if (!dojo._hasResource["dojo.dnd.Container"]) { //_hasResource checks added by build. Do not use _hasResource directly in your code.
    dojo._hasResource["dojo.dnd.Container"] = true;
    dojo.provide("dojo.dnd.Container");

    dojo.require("dojo.dnd.common");
    dojo.require("dojo.parser");

    /*
     Container states:
     ""		- normal state
     "Over"	- mouse over a container
     Container item states:
     ""		- normal state
     "Over"	- mouse over a container item
     */

    dojo.declare("dojo.dnd.Container", null, {
        // summary: a Container object, which knows when mouse hovers over it,
        //	and know over which element it hovers

        // object attributes (for markup)
        skipForm: false,

        constructor: function(node, params) {
            // summary: a constructor of the Container
            // node: Node: node or node's id to build the container on
            // params: Object: a dict of parameters, recognized parameters are:
            //	creator: Function: a creator function, which takes a data item, and returns an object like that:
            //		{node: newNode, data: usedData, type: arrayOfStrings}
            //	skipForm: Boolean: don't start the drag operation, if clicked on form elements
            //	_skipStartup: Boolean: skip startup(), which collects children, for deferred initialization
            //		(this is used in the markup mode)
            this.node = dojo.byId(node);
            if (!params) {
                params = {};
            }
            this.creator = params.creator || null;
            this.skipForm = params.skipForm;
            this.defaultCreator = dojo.dnd._defaultCreator(this.node);

            // class-specific variables
            this.map = {};
            this.current = null;

            // states
            this.containerState = "";
            dojo.addClass(this.node, "dojoDndContainer");

            // mark up children
            if (!(params && params._skipStartup)) {
                this.startup();
            }

            // set up events
            this.events = [
                dojo.connect(this.node, "onmouseover", this, "onMouseOver"),
                dojo.connect(this.node, "onmouseout", this, "onMouseOut"),
                // cancel text selection and text dragging
                dojo.connect(this.node, "ondragstart", this, "onSelectStart"),
                dojo.connect(this.node, "onselectstart", this, "onSelectStart")
            ];
        },

        // object attributes (for markup)
        creator: function() {
        },    // creator function, dummy at the moment

        // abstract access to the map
        getItem: function(/*String*/ key) {
            // summary: returns a data item by its key (id)
            return this.map[key];	// Object
        },
        setItem: function(/*String*/ key, /*Object*/ data) {
            // summary: associates a data item with its key (id)
            this.map[key] = data;
        },
        delItem: function(/*String*/ key) {
            // summary: removes a data item from the map by its key (id)
            delete this.map[key];
        },
        forInItems: function(/*Function*/ f, /*Object?*/ o) {
            // summary: iterates over a data map skipping members, which
            //	are present in the empty object (IE and/or 3rd-party libraries).
            o = o || dojo.global;
            var m = this.map, e = dojo.dnd._empty;
            for (var i in this.map) {
                if (i in e) {
                    continue;
                }
                f.call(o, m[i], i, m);
            }
        },
        clearItems: function() {
            // summary: removes all data items from the map
            this.map = {};
        },

        // methods
        getAllNodes: function() {
            // summary: returns a list (an array) of all valid child nodes
            return dojo.query("> .dojoDndItem", this.parent);	// NodeList
        },
        insertNodes: function(data, before, anchor) {
            // summary: inserts an array of new nodes before/after an anchor node
            // data: Array: a list of data items, which should be processed by the creator function
            // before: Boolean: insert before the anchor, if true, and after the anchor otherwise
            // anchor: Node: the anchor node to be used as a point of insertion
            if (!this.parent.firstChild) {
                anchor = null;
            } else if (before) {
                if (!anchor) {
                    anchor = this.parent.firstChild;
                }
            } else {
                if (anchor) {
                    anchor = anchor.nextSibling;
                }
            }
            if (anchor) {
                for (var i = 0; i < data.length; ++i) {
                    var t = this._normalizedCreator(data[i]);
                    this.setItem(t.node.id, {data: t.data, type: t.type});
                    this.parent.insertBefore(t.node, anchor);
                }
            } else {
                for (var i = 0; i < data.length; ++i) {
                    var t = this._normalizedCreator(data[i]);
                    this.setItem(t.node.id, {data: t.data, type: t.type});
                    this.parent.appendChild(t.node);
                }
            }
            return this;	// self
        },
        destroy: function() {
            // summary: prepares the object to be garbage-collected
            dojo.forEach(this.events, dojo.disconnect);
            this.clearItems();
            this.node = this.parent = this.current;
        },

        // markup methods
        markupFactory: function(params, node) {
            params._skipStartup = true;
            return new dojo.dnd.Container(node, params);
        },
        startup: function() {
            // summary: collects valid child items and populate the map

            // set up the real parent node
            this.parent = this.node;
            if (this.parent.tagName.toLowerCase() == "table") {
                var c = this.parent.getElementsByTagName("tbody");
                if (c && c.length) {
                    this.parent = c[0];
                }
            }

            // process specially marked children
            this.getAllNodes().forEach(function(node) {
                if (!node.id) {
                    node.id = dojo.dnd.getUniqueId();
                }
                var type = node.getAttribute("dndType"),
                        data = node.getAttribute("dndData");
                this.setItem(node.id, {
                    data: data ? data : node.innerHTML,
                    type: type ? type.split(/\s*,\s*/) : ["text"]
                });
            }, this);
        },

        // mouse events
        onMouseOver: function(e) {
            // summary: event processor for onmouseover
            // e: Event: mouse event
            var n = e.relatedTarget;
            while (n) {
                if (n == this.node) {
                    break;
                }
                try {
                    n = n.parentNode;
                } catch(x) {
                    n = null;
                }
            }
            if (!n) {
                this._changeState("Container", "Over");
                this.onOverEvent();
            }
            n = this._getChildByEvent(e);
            if (this.current == n) {
                return;
            }
            if (this.current) {
                this._removeItemClass(this.current, "Over");
            }
            if (n) {
                this._addItemClass(n, "Over");
            }
            this.current = n;
        },
        onMouseOut: function(e) {
            // summary: event processor for onmouseout
            // e: Event: mouse event
            for (var n = e.relatedTarget; n;) {
                if (n == this.node) {
                    return;
                }
                try {
                    n = n.parentNode;
                } catch(x) {
                    n = null;
                }
            }
            if (this.current) {
                this._removeItemClass(this.current, "Over");
                this.current = null;
            }
            this._changeState("Container", "");
            this.onOutEvent();
        },
        onSelectStart: function(e) {
            // summary: event processor for onselectevent and ondragevent
            // e: Event: mouse event
            if (!this.skipForm || !dojo.dnd.isFormElement(e)) {
                dojo.stopEvent(e);
            }
        },

        // utilities
        onOverEvent: function() {
            // summary: this function is called once, when mouse is over our container
        },
        onOutEvent: function() {
            // summary: this function is called once, when mouse is out of our container
        },
        _changeState: function(type, newState) {
            // summary: changes a named state to new state value
            // type: String: a name of the state to change
            // newState: String: new state
            var prefix = "dojoDnd" + type;
            var state = type.toLowerCase() + "State";
            //dojo.replaceClass(this.node, prefix + newState, prefix + this[state]);
            dojo.removeClass(this.node, prefix + this[state]);
            dojo.addClass(this.node, prefix + newState);
            this[state] = newState;
        },
        _addItemClass: function(node, type) {
            // summary: adds a class with prefix "dojoDndItem"
            // node: Node: a node
            // type: String: a variable suffix for a class name
            dojo.addClass(node, "dojoDndItem" + type);
        },
        _removeItemClass: function(node, type) {
            // summary: removes a class with prefix "dojoDndItem"
            // node: Node: a node
            // type: String: a variable suffix for a class name
            dojo.removeClass(node, "dojoDndItem" + type);
        },
        _getChildByEvent: function(e) {
            // summary: gets a child, which is under the mouse at the moment, or null
            // e: Event: a mouse event
            var node = e.target;
            if (node) {
                for (var parent = node.parentNode; parent; node = parent,parent = node.parentNode) {
                    if (parent == this.parent && dojo.hasClass(node, "dojoDndItem")) {
                        return node;
                    }
                }
            }
            return null;
        },
        _normalizedCreator: function(item, hint) {
            // summary: adds all necessary data to the output of the user-supplied creator function
            var t = (this.creator ? this.creator : this.defaultCreator)(item, hint);
            if (!dojo.isArray(t.type)) {
                t.type = ["text"];
            }
            if (!t.node.id) {
                t.node.id = dojo.dnd.getUniqueId();
            }
            dojo.addClass(t.node, "dojoDndItem");
            return t;
        }
    });

    dojo.dnd._createNode = function(tag) {
        // summary: returns a function, which creates an element of given tag
        //	(SPAN by default) and sets its innerHTML to given text
        // tag: String: a tag name or empty for SPAN
        if (!tag) {
            return dojo.dnd._createSpan;
        }
        return function(text) {    // Function
            var n = dojo.doc.createElement(tag);
            n.innerHTML = text;
            return n;
        };
    };

    dojo.dnd._createTrTd = function(text) {
        // summary: creates a TR/TD structure with given text as an innerHTML of TD
        // text: String: a text for TD
        var tr = dojo.doc.createElement("tr");
        var td = dojo.doc.createElement("td");
        td.innerHTML = text;
        tr.appendChild(td);
        return tr;	// Node
    };

    dojo.dnd._createSpan = function(text) {
        // summary: creates a SPAN element with given text as its innerHTML
        // text: String: a text for SPAN
        var n = dojo.doc.createElement("span");
        n.innerHTML = text;
        return n;	// Node
    };

    // dojo.dnd._defaultCreatorNodes: Object: a dicitionary, which maps container tag names to child tag names
    dojo.dnd._defaultCreatorNodes = {ul: "li", ol: "li", div: "div", p: "div"};

    dojo.dnd._defaultCreator = function(node) {
        // summary: takes a container node, and returns an appropriate creator function
        // node: Node: a container node
        var tag = node.tagName.toLowerCase();
        var c = tag == "table" ? dojo.dnd._createTrTd :
                dojo.dnd._createNode(dojo.dnd._defaultCreatorNodes[tag]);
        return function(item, hint) {    // Function
            var isObj = dojo.isObject(item) && item;
            var data = (isObj && item.data) ? item.data : item;
            var type = (isObj && item.type) ? item.type : ["text"];
            var t = String(data), n = (hint == "avatar" ? dojo.dnd._createSpan : c)(t);
            n.id = dojo.dnd.getUniqueId();
            return {node: n, data: data, type: type};
        };
    };

}
